198 打家劫舍
是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
输入:[1,2,3,1] ** 输出:**4 ** 解释:** 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入:[2,7,9,3,1] ** 输出:**12 ** 解释:** 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。 偷窃到的最高金额 = 2 + 9 + 1 = 12 。
动态规划入门题
1、标准递归搜索 从最后一个值来考虑,偷不偷最后一家 不偷的话是 dfs (i-1) 偷的话是 dfs (i-2) + nums [i] dfs(i) = max(dfs(i-1), dfs(i-2) + nums[i])
ts
var rob = function(nums) {
const n = nums.length;
// dfs(i) 表示从 nums[0] 到 nums[i] 最多能偷多少
function dfs(i) {
if (i < 0) { // 递归边界(没有房子)
return 0;
}
const res = Math.max(dfs(i - 1), dfs(i - 2) + nums[i]);
return res;
}
return dfs(n - 1); // 从最后一个房子开始思考
};
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
2、记忆化搜索 上面的递归搜索存在大量重复计算,可以用一个数组 memo 来记录已经计算过的结果
ts
var rob = function(nums) {
const n = nums.length;
const memo = Array(n).fill(-1); // -1 表示没有计算过
// dfs(i) 表示从 nums[0] 到 nums[i] 最多能偷多少
function dfs(i) {
if (i < 0) { // 递归边界(没有房子)
return 0;
}
if (memo[i] !== -1) { // 之前计算过
return memo[i];
}
const res = Math.max(dfs(i - 1), dfs(i - 2) + nums[i]);
memo[i] = res; // 记忆化:保存计算结果
return res;
}
return dfs(n - 1); // 从最后一个房子开始思考
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
3、递推 递归是从上到下再到上 递推是从下到上
f 数组下标 + 2 防止数组越界
ts
var rob = function(nums) {
const n = nums.length;
const f = Array(n + 2).fill(0);
for (let i = 0; i < n; i++) {
f[i + 2] = Math.max(f[i + 1], f[i] + nums[i]);
}
return f[n + 1];
};
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
4、递推优化 由于 f [i] 只和 f [i-1] 和 f [i-2] 有关,可以用两个变量代替数组
ts
var rob = function(nums) {
let f0 = 0, f1 = 0;
for (const x of nums) {
[f0, f1] = [f1, Math.max(f1, f0 + x)]
}
return f1;
};
1
2
3
4
5
6
7
2
3
4
5
6
7
O(2^n) -> O(n) -> O(n) -> O(n)(O(1))